// Copyright 2013 The Go Circuit Project
// Use of this source code is governed by the license for
// The Go Circuit Project, found in the LICENSE file.
//
// Authors:
//   2013 Petar Maymounkov <p@gocircuit.org>

package ns

import (
	"io"
	"sync"
	"time"

	"circuit/kit/iomisc"
	"circuit/kit/lang"
	"circuit/kit/rh"
)

//
type commonFID struct {
	rh.ZeroFID
	//
	gtm  time.Time
	name string
	q    rh.Q
	//
	r iomisc.InterruptibleReader
	w iomisc.InterruptibleWriter
	//
	sync.Mutex
	n int64
}

//
type WalkFunc func() rh.FID

func (cmn *commonFID) add(d int) int64 {
	cmn.Lock()
	defer cmn.Unlock()
	cmn.n += int64(d)
	return cmn.n
}

func (cmn *commonFID) init(name string) {
	cmn.gtm = time.Now()
	cmn.name = name
	cmn.q = rh.Q{
		ID:  uint64(lang.ComputeReceiverID(cmn)),
		Ver: 1,
	}
	cmn.n = 0
}

func (cmn *commonFID) String() string {
	return "read/writer"
}

func (cmn *commonFID) Q() rh.Q {
	return cmn.q
}

func (cmn *commonFID) Open(rh.Flag, rh.Intr) error {
	return nil
}

func (cmn *commonFID) Clunk() {
	if cmn.r != nil {
		cmn.r.Close()
	}
	if cmn.w != nil {
		cmn.w.Close()
	}
}

func (cmn *commonFID) Stat() (*rh.Dir, error) {
	return &rh.Dir{
		Q:      cmn.q,
		Mode:   rh.Mode{Attr: rh.ModeIO},
		Atime:  cmn.gtm,
		Mtime:  cmn.gtm,
		Name:   cmn.name,
		Length: 0,
		Uid:    uid(),
		Gid:    gid(),
	}, nil
}

// readerFID
type readerFID struct {
	commonFID
}

//
func NewReaderFID(name string, r io.ReadCloser) UnregulatedFID {
	fid := &readerFID{}
	ir, iw := iomisc.InterruptiblePipe()
	go func() {
		io.Copy(iw, r)
		iw.Close()
	}()
	//
	fid.commonFID.init(name)
	fid.commonFID.r = ir
	return fid
}

//
func NewPipeReaderFID(name string) (UnregulatedFID, iomisc.InterruptibleWriter) {
	fid := &readerFID{}
	fid.commonFID.init(name)
	fid.commonFID.r, fid.commonFID.w = iomisc.InterruptiblePipe()
	return fid, fid.commonFID.w
}

func (r *readerFID) Stat() (dir *rh.Dir, err error) {
	dir, _ = r.commonFID.Stat()
	dir.Perm = 0444 // r--r--r--
	return dir, nil
}

func (r *readerFID) Read(_ int64, count int, intr rh.Intr) (chunk rh.Chunk, err error) {
	var buf = make(rh.ByteChunk, count)
	n, err := r.commonFID.r.ReadIntr(buf, intr)
	r.commonFID.add(n)
	if err != nil {
		if err == io.EOF {
			err = nil // Don't report EOF, since FUSE doesn't like it on regular files.
		} else {
			err = rh.ErrIO
		}
		return buf[:n], err
	}
	return buf[:n], nil
}

// writerFID
type writerFID struct {
	commonFID
}

//
func NewWriterFID(name string, w io.WriteCloser) UnregulatedFID {
	fid := &writerFID{}
	ir, iw := iomisc.InterruptiblePipe()
	go func() {
		io.Copy(w, ir)
		w.Close()
	}()
	//
	fid.commonFID.init(name)
	fid.commonFID.w = iw
	return fid
}

//
func NewPipeWriterFID(name string) (UnregulatedFID, iomisc.InterruptibleReader) {
	fid := &writerFID{}
	fid.commonFID.init(name)
	fid.commonFID.r, fid.commonFID.w = iomisc.InterruptiblePipe()
	return fid, fid.commonFID.r
}

func (w *writerFID) Stat() (dir *rh.Dir, err error) {
	dir, _ = w.commonFID.Stat()
	dir.Perm = 0222 // -w--w--w-
	return dir, nil
}

func (w *writerFID) Write(_ int64, data rh.Chunk, intr rh.Intr) (n int, err error) {
	if data == nil {
		return 0, nil
	}
	chnk, ok := data.(rh.ByteChunk)
	if !ok {
		return 0, rh.ErrClash
	}
	n, err = w.commonFID.w.WriteIntr(chnk, intr)
	w.commonFID.add(n)
	if err != nil {
		return n, rh.ErrIO
	}
	return n, nil
}
